home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
system
/
admin
/
linuxcon.000
/
linuxcon
/
linuxconf-1.6
/
userconf
/
user.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-07-22
|
18KB
|
744 lines
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include "internal.h"
#include "userconf.h"
#include "../paths.h"
//#include "../xconf/xconf.h"
#include "userconf.m"
static USERCONF_HELP_FILE help_user ("user");
USERCONF_HELP_FILE help_password ("password");
PRIVATE void USER::init(
const char *_name,
const char *_passwd,
int _uid,
int _gid,
const char *_gecos,
const char *_dir,
const char *_shell)
{
name.setfrom (_name);
passwd.setfrom (_passwd);
uid = _uid;
gid = _gid;
comment.setfrom (_gecos);
wrkdir.setfrom (_dir);
shell.setfrom (_shell);
special = !shells_isok(_shell);
}
PUBLIC USER::USER(
const char *_name,
const char *_passwd,
int _uid,
int _gid,
const char *_gecos,
const char *_dir,
const char *_shell)
{
init (_name,_passwd,_uid,_gid,_gecos,_dir,_shell);
}
PUBLIC USER::USER()
{
init ("","",-1,-1,"","","");
}
/*
Decompose a /etc/passwd style line into words
*/
int user_splitline (const char *line, char words[9][100])
{
int nbword = 0;
char *dst = words[0];
for (int i=0; i<9; i++) words[i][0] = '\0';
while (*line != '\0' && *line != '\n'){
if (*line == ':'){
line++;
*dst = '\0';
nbword++;
dst = words[nbword];
}else{
*dst++ = *line++;
}
}
*dst = '\0';
return nbword;
}
/*
Taken from /etc/passwd directly
*/
PUBLIC USER::USER(const char *line)
{
char words[9][100];
user_splitline (line,words);
name.setfrom (words[0]);
passwd.setfrom (words[1]);
uid = atoi(words[2]);
gid = atoi(words[3]);
comment.setfrom (words[4]);
wrkdir.setfrom (words[5]);
shell.setfrom (words[6]);
special = !shells_isok(words[6]);
}
PUBLIC USER::USER(struct passwd *p)
{
init (p->pw_name,p->pw_passwd,p->pw_uid,p->pw_gid,p->pw_gecos,p->pw_dir
,p->pw_shell);
}
PUBLIC USER::~USER()
{
}
/*
Return the crypt password field
*/
PUBLIC const char *USER::getpwd()
{
return passwd.get();
}
/*
Write one record of /etc/passwd
*/
PUBLIC void USER::write(FILE *fout)
{
fprintf (fout,"%s:%s:%d:%d:%s:%s:%s\n"
,name.get(),passwd.get(),uid,gid,comment.get()
,wrkdir.get(),shell.get());
}
/*
Return the login name of the user.
*/
PUBLIC const char *USER::getname()
{
return name.get();
}
/*
Return the long informative name of the user.
*/
PUBLIC const char *USER::getgecos()
{
return comment.get();
}
/*
Return the User ID of the user.
*/
PUBLIC int USER::getuid()
{
return uid;
}
/*
Return the User GID of the user.
*/
PUBLIC int USER::getgid()
{
return gid;
}
/*
Return the shell (path) of the user.
If the user has no explicit shell, return the default.
*/
PUBLIC const char *USER::getshell()
{
return shell.is_empty() ? shells_getdefault() : shell.get();
}
/*
Return != 0 if this user account is a special admin account
unrelated to any user.
*/
PUBLIC int USER::is_admin()
{
const char *pt = name.get();
return strcmp(pt,"root")==0
|| strcmp(pt,"adm")==0
|| strcmp(pt,"bin")==0
|| strcmp(pt,"daemon")==0
|| strcmp(pt,"sys")==0
|| strcmp(pt,"postmaster")==0
|| strcmp(pt,"usenet")==0
|| strcmp(pt,"sync")==0;
}
/*
Return != 0 if this user account is a special non user account
generally intended for machine to machine connection like uucp.
*/
PUBLIC int USER::is_special()
{
return special;
}
/*
Check if a user is correctly configured.
Return -1 if not.
*/
PRIVATE int USER::check(
USERS &users,
GROUPS &groups,
int full) // Check everything, even directory
// access and so on
{
char status[1000];
status[0] = '\0';
USER *other = users.getitem(name.get());
if (other != NULL && other != this){
strcat (status,MSG_U(E_DUPLOGIN,"User already exist (login name)\n"));
}
other = users.getfromuid(uid);
if (other != NULL && other != this){
strcat (status,MSG_U(E_DUPID,"User already exist (User ID)\n"));
}
if (groups.getfromgid(gid)==NULL){
strcat (status,MSG_U(E_UNKNOWNGRP,"Group do not exist\n"));
}
if (!special && !shells_isok(shell.get())){
strcat (status,MSG_U(E_IVLDSHELL,"Invalid command interpretor\n"));
}else if (!shells_exist(shell.get())){
strcat (status,MSG_U(E_NOSHELL,"Command interpretor not available\n"));
}
char status_dir[200];
int code_dir = checkhome(status_dir);
/* #Specification: userconf / user / home directory
userconf do not allow a user to have an empty directory
nor a directory which point to something else than
a directory.
*/
if (code_dir != 0 && (full || code_dir != ENOENT)){
strcat (status,status_dir);
}
int ret = 0;
if (status[0] != '\0'){
xconf_error ("%s",status);
ret = -1;
}
return ret;
}
/*
Check if the home of the user do exist
Return -1 : No home directory specified
ENOENT : path does not exist.
ENOTDIR : path do exist, but is not a directory.
EPERM : path do exist but permissions are wrong
0 : all is ok.
*/
PUBLIC int USER::checkhome (
char *status) // Will contain an explanation of the problem
// if any (may be NULL)
{
int ret = -1;
const char *msg = MSG_U(E_NOHOME,"No home directory specified");
if (!wrkdir.is_empty()){
struct stat st;
if (stat(wrkdir.get(),&st)!=-1){
if (S_ISDIR (st.st_mode)){
/* #Specification: user / home directory / owner
userconf check that the home directory
of a user is own (gid and uid) by him
except for special account (
administrative, PPP, uucp, etc...)
where we generally allocate the same
directory to a bunch of users.
*/
if (special
|| (st.st_uid == uid && st.st_gid == gid)){
ret = 0;
msg = MSG_U(N_IS_OK,"is ok");
}else{
ret = EPERM;
if (st.st_uid == uid){
msg = MSG_U(E_IVLDGRP,"have invalid group");
}else if (st.st_gid == gid){
msg = MSG_U(E_IVLDOWN,"have invalid owner");
}else{
msg = MSG_U(E_IVLDG_O,"have invalid owner and group");
}
}
}else{
ret = ENOTDIR;
msg = MSG_U(E_EXISTDIR,"do exist, but is not a directory");
}
}else{
msg = MSG_U(E_DONOTEXIST,"does not exist");
ret = ENOENT;
}
}
if (status != NULL){
sprintf (status
,MSG_U(E_HOMEUSER,"Home directory of user %s: %s\n%s\n")
,name.get(),wrkdir.get(),msg);
}
return ret;
}
/*
Set the name of the new user
*/
PUBLIC void USER::setname(const char *_name)
{
if (_name != NULL) name.setfrom (_name);
}
/*
Create/update the user home directory.
Return -1 if any error.
*/
PUBLIC int USER::sethome ()
{
char status[200];
int ret = checkhome(status);
if (ret != 0){
if (ret == -1 || ret == ENOTDIR){
xconf_error ("%s",status);
}else if (perm_rootaccess(
MSG_U(P_SETUSERDIR,"set user %s directory\n")
,name.get())){
if (ret == ENOENT){
ret = mkdir (wrkdir.get(),0755);
if (ret == 0) ret = EPERM;
}
if (ret == EPERM){
if (chown (wrkdir.get(),uid,gid) != -1){
ret = 0;
}
}
if (ret != 0){
xconf_error (
MSG_U(E_SETUPDIR
,"Can't setup user %s's home directory %s\n"
"reason: %s\n")
,name.get(),wrkdir.get()
,strerror (errno));
}
}
}
return ret;
}
static int user_str2gid(
GROUPS &groups,
SSTRING &group)
{
/* #Specification: user record / gid / format
GID may be entered either as a string (a group name)
or as a number.
*/
const char *str = group.get();
int gid = groups.getgid(str);
if (gid == -1 && isdigit(str[0])) gid = atoi(str);
return gid;
}
/*
Edit the specification of a user.
Return -1 if the user escape without accepting the changes.
Return 0 if the user accepted the change
Return 1 if the user wish to delete this record.
*/
PUBLIC int USER::edit(USERS &users, GROUPS &groups, int is_new)
{
DIALOG dia;
dia.newf_str (MSG_U(F_LOGIN,"Login name"),name);
dia.newf_str (MSG_U(F_FULLNAME,"Full name"),comment);
SSTRING group;
group.setfrom (gid);
if (gid == -1){
group.setfrom (groups.getdefault());
shell.setfrom (shells_getdefault());
}else{
GROUP *grp = groups.getfromgid(gid);
if (grp != NULL){
group.setfrom (grp->getname());
}
}
FIELD_COMBO *grpl = dia.newf_combo (MSG_U(F_GROUP,"group"),group);
{
groups.sortbyname();
for (int i=0; i<groups.getnb(); i++){
grpl->addopt (groups.getitem(i)->getname());
}
}
FIELD *fhome = dia.newf_str (MSG_U(F_HOME,"Home directory(opt)")
,wrkdir);
dia.newf_str (MSG_U(F_SHELL,"Command interpreter(opt)")
,shell);
SSTRING struid;
if (uid != -1) struid.setfrom (uid);
FIELD *fuid = dia.newf_str (MSG_U(F_UID,"User ID(opt)"),struid);
if (is_special()){
fhome->set_readonly();
grpl->set_readonly();
fuid->set_readonly();
}
SHADOW *shadow = NULL;
int add_shadow = 0;
if (shadow_exist()){
shadow = users.getshadow(this);
if (shadow == NULL){
shadow = new SHADOW;
shadow->passwd.setfrom (passwd);
passwd.setfrom ("x");
add_shadow = 1;
}
dia.newf_title ("",MSG_U(T_PASSMNG,"Password management"));
dia.newf_num (MSG_U(F_PASSMAY,"Must keep # days"),shadow->may);
dia.newf_num (MSG_U(F_PASSMUST,"Must change after # days"),shadow->must);
dia.newf_num (MSG_U(F_PASSWARN,"Warn # days before expieration"),shadow->warn);
dia.newf_num (MSG_U(F_PASSEXPIRE,"Account expire after # days"),shadow->expire);
}
int field = 0;
int ret = -1;
while (1){
MENU_STATUS code = dia.edit (
MSG_U(T_USERINFO,"User information")
,MSG_U(I_USERINTRO
,"You must specify at least the name\n"
"and the full name")
,help_user.getpath()
,field
,MENUBUT_ACCEPT|MENUBUT_CANCEL|MENUBUT_DEL);
if (code == MENU_CANCEL || code == MENU_ESCAPE){
break;
}else if (!perm_rootaccess(
MSG_U(P_USERDATA
,"to maintain the user database"))){
dia.restore();
}else if (code == MENU_DEL){
if (xconf_areyousure(MSG_U(Q_DELUSER
,"Confirm deletion of user account"))){
ret = 1;
break;
}
}else{
/* #Specification: userconf / user account / semicolon
A check is made to ensure that the user
has not entered a : in any field
during edition.
*/
if (name.strchr(':') != NULL
|| comment.strchr(':') != NULL
|| group.strchr(':') != NULL
|| struid.strchr(':') != NULL
|| shell.strchr (':') != NULL
|| wrkdir.strchr (':') != NULL){
xconf_error (MSG_U(E_NO2PT
,"No : in any field allowed"));
}else{
gid = user_str2gid(groups,group);
uid = struid.is_empty()
? users.getnewuid(gid)
: struid.getval();
if (wrkdir.is_empty()){
/* #todo: userconf / default home directory
We currently force every new user into /home/...
unless specified. This should be configurable
maybe.
*/
char buf[50];
sprintf (buf,"/home/%s",name.get());
wrkdir.setfrom (buf);
}
if (check(users,groups,0)==0){
/* #Specification: user edit / bad html dialog
This is a good exemple of a bad dialog
(or at least complex dialog) for html mode
All side effect of the dialog must be done
at the exit. The do_sethome kludge is there
just for that.
Anyway, this dialog is bad and should contain
the passwords (2 lines) when creating a new
account. Flat dialog are generally nicer.
*/
int do_sethome = 0;
char status[200];
int code = checkhome (status);
if (code == ENOTDIR){
xconf_error ("%s",status);
}else if (code != 0){
if (code == ENOENT){
strcat (status
,MSG_U(Q_CREATE,"\nDo you want to create it ?"));
}else if (code == EPERM){
strcat (status
,MSG_U(Q_FIXIT,"\nDo you want to fix it ?"));
}
if (xconf_yesno(
MSG_U(Q_USERHOME,"User home directory")
,status,help_nil)==MENU_YES){
do_sethome = 1;
}
}
if (checkhome(NULL)==0 || do_sethome){
ret = 0;
if (is_new) ret = editpass(1,shadow);
if (ret == 0){
if (do_sethome) sethome();
setmodified();
if (shadow != NULL){
shadow->name.setfrom (name);
if (add_shadow) users.addshadow (shadow);
}
}
break;
}
}
}
}
}
if (ret != 0){
dia.restore();
gid = user_str2gid(groups,group);
uid = struid.getval();
}
return ret;
}
/*
Check if a password is weak
*/
static int pass_isweak(const char *pass)
{
/* #Specification: userconf / net password / rejected
New password are validated with some rules to ensure
they are difficult enough. Here are the rules.
#
-6 chars minimum
-Must have at least one non-letter character
#
*/
int ret = 1;
if (pass[0] == '\0' || strcmp(pass,"*")==0){
/* #Specification: userconf / user password / empty and *
Only root is allowed to set an empty password or one
with only * in it.
It will be refused (error message) for all other
users.
*/
if (getuid()==0){
ret = 0;
}else{
xconf_error (
MSG_U(E_NULLPASS,"Empty password not allowed.\n"
"Only root is allowed to do so.\n"
"This is not a good idea though!"));
}
}else{
PASSWD_VALID vl;
/* #Specification: userconf / password / checking
userconf check the minimum length and the
amount of non alpha character in a new password.
If the new password does not fullfill the local
policies, it is rejected.
*/
if ((int)strlen(pass)>=vl.minlen){
int nbalpha = 0;
while (*pass != '\0'){
if (!isalpha(*pass)) nbalpha++;
pass++;
}
if (nbalpha >= vl.minnonalpha) ret = 0;
}
if (ret){
xconf_error (MSG_U(E_WEAKPASS
,"Password not accepted\n"
"Select a more complicated one\n"
"The local policies are\n"
"\n"
"Minimum length : %d\n"
"Minimum number of non-alpha character : %d\n")
,vl.minlen,vl.minnonalpha);
}
}
return ret;
}
/*
Update the passwd field with a new password and manage SHADOW
*/
PRIVATE void USER::update_passwd (
const char *newp,
SHADOW *shadow,
int is_lock)
{
SSTRING *pwd = &passwd;
if (shadow != NULL){
time_t tim = time(NULL);
int days = tim/(24*60*60);
if (is_lock){
if (shadow->disable == 0){
shadow->disable = days;
shadow->last = days;
}
}else{
pwd = &shadow->passwd;
shadow->last = days;
shadow->disable = 0;
passwd.setfrom ("x");
}
}
char buf[80];
char *store = buf;
strcpy (buf,newp);
if (buf[0] != '\0' && strcmp(buf,"*")!=0){
store = crypt(buf,buf);
}
pwd->setfrom (store);
}
/*
Edit(set) the password of a user.
Return -1 if the user escape without accepting the changes.
If the user enter the password correctly and accept it, the
object USER is updated with the crypted version.
*/
PUBLIC int USER::editpass(
int lock_available, // The dialog allow locking the account
// Putting * in the password
// or managing the SHADOW record properly
SHADOW *shadow)
{
int ret = -1;
while (1){
DIALOG dia;
char is_lock = passwd.cmp("*")==0;
if (shadow) is_lock = shadow->disable != 0;
if (lock_available){
dia.newf_chk ("",is_lock,MSG_U(F_LOCKACCOUNT,"Lock the account"));
}
SSTRING buf1;
dia.newf_pass (MSG_U(F_PASSWORD,"Password"),buf1);
SSTRING buf2;
dia.newf_pass (MSG_U(F_CONFIRM,"Confirmation"),buf2);
char title[80];
sprintf (title,MSG_U(T_PASSWORD,"%s's password"),name.get());
if (dia.edit (title
,MSG_U(I_PASSINTRO
,"You must enter the new password twice\n"
"To make sure you have enter it\n"
"correctly.\n")
,help_password.getpath()
,0) != MENU_ACCEPT){
break;
}else if (lock_available && is_lock){
if (buf1.is_empty() && buf2.is_empty()){
update_passwd ("*",shadow,1);
setmodified();
ret = 0;
break;
}else{
xconf_error (MSG_U(E_LOCKORNOT
,"A locked account does not have a password\n"
"either you type a password(twice)\n"
"either you lock the account and then\n"
"you don't enter any passwords"));
}
}else if (buf1.cmp(buf2)!=0){
xconf_error (MSG_U(E_MISMATCH
,"There was a mismatch\n"
"Please try again\n"));
}else if (!pass_isweak(buf1.get())){
xconf_notice (MSG_U(N_ACCEPT,"New password for user %s accepted")
,name.get());
update_passwd (buf1.get(),shadow,0);
setmodified();
ret = 0;
break;
}
}
return ret;
}
/*
Edit the password of the current user.
Ask for his current password to allow him to continue.
*/
PUBLIC int USER::edithispass(SHADOW *shadow)
{
/* #Specification: userconf / passwd clone
userconf can be a clone of the /bin/passwd program, If the
proper symlink is done. When a user attempt to change
his own password, the program prompt for the current one.
*/
int ret = -1;
char buf1[MAX_LEN+1];
if (xconf_inputpass (
MSG_U(T_CHGYOURPASS,"Changing your password")
,MSG_U(I_ENTERYOURPASS,"Please enter your current password\n")
,help_password
,buf1)==MENU_ACCEPT){
if (strcmp(crypt(buf1,passwd.get()),passwd.get())==0){
ret = editpass(0,shadow);
}else{
xconf_error (MSG_R(E_IVLDPASS));
}
}
return ret;
}
/*
non tty oriented passwd changing facility
copy the functionnality of the old passwd program.
*/
PUBLIC int USER::edithispass_notty(SHADOW *shadow)
{
int ret = -1;
printf (MSG_U(W_CHGPASS,"Changing password for %s\n"),getname());
printf (MSG_U(Q_ENTEROLDPASS,"Enter old password:"));
fflush (stdout);
char old[100];
if (fgets (old,sizeof(old)-1,stdin) != NULL){
printf (MSG_U(Q_ENTERNEWPASS,"Enter new password:"));
fflush (stdout);
char newp[100];
if (fgets (newp,sizeof(newp)-1,stdin) != NULL){
printf (MSG_U(Q_RETYPE,"Re-type new password:"));
fflush (stdout);
char newp2[100];
if (fgets (newp2,sizeof(newp2)-1,stdin) != NULL){
if (strcmp(crypt(old,passwd.get()),passwd.get())!=0){
}else if (strcmp(newp,newp2)!=0){
}else if (pass_isweak(newp)){
}else{
update_passwd (newp,shadow,0);
ret = 0;
}
}
}
}
return ret;
}
#ifdef TEST
int main (int argc, char *argv[])
{
dialog_clear();
USERS users;
GROUPS groups;
if (argc == 1){
USER *user = new USER;
if (user->edit(users,groups,1)==0){
users.add (user);
users.write();
}else{
delete user;
}
}else{
USER *user = users.getitem(argv[1]);
if (user != NULL && user.edit(users,groups,0)==0){
users.write();
}
}
return 0;
}
#endif